package cn.tedu.mall.seckill.timer.job;

import cn.tedu.mall.pojo.seckill.model.SeckillSku;
import cn.tedu.mall.pojo.seckill.model.SeckillSpu;
import cn.tedu.mall.seckill.mapper.SeckillSkuMapper;
import cn.tedu.mall.seckill.mapper.SeckillSpuMapper;
import cn.tedu.mall.seckill.utils.SeckillCacheUtils;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;

@Slf4j
public class SeckillInitialJob implements Job {

    @Autowired
    private SeckillSpuMapper spuMapper;
    @Autowired
    private SeckillSkuMapper skuMapper;

    // 查询出来的数据要保存到Redis中
    @Autowired
    private RedisTemplate redisTemplate;

    //  上面的redis对象是向redis中保存对象用的,内部会将数据序列化后
    //    以上二进制的格式保存在redis中,读写速度快,但是数据不能修改
    //    这种设计无法在Redis内部完成对象属性或值的修改
    //      库存是一个数字,redis支持直接在redis内部对数字进行增减减少java操作
    //      声明一个用字符串操作redis的对象
    @Autowired
    private StringRedisTemplate  stringRedisTemplate;

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        // 开始前将商品库存保存在redis   运行时距离秒杀还有5分钟
        //  查新5分钟要参与秒杀的商品
        LocalDateTime time = LocalDateTime.now().plusMinutes(5);
        // 查询这个时间的所有秒杀商品
        List<SeckillSpu> seckillSpus = spuMapper.findSeckillSpusByTime(time);
        //  遍历seckillSpus
        //  将这些商品 对应的sku库存保存到redis
        //    为了方便随机数生成定一个对象
        Random ran = new Random();
        for (SeckillSpu spu : seckillSpus) {
            List<SeckillSku> seckillSkus = skuMapper.findSeckillSkusBySpuId(spu.getSpuId());
            // 遍历当前spu的所有sku列表
            for (SeckillSku sku : seckillSkus) {
                log.info("开始将{}号商品保存到Redis",sku.getSkuId());

                String skuStockKey = SeckillCacheUtils.getStockKey(sku.getSkuId());
                // 检查redis是否包含了这个key
                if (redisTemplate.hasKey(skuStockKey)){
                    //
                    log.info("{}号商品已经在缓存中",sku.getSkuId());
                }else {
                    // 不在缓存中,就将库存数据保存到Redis
                    // 利用stringRedisTemplate 保存,方便减少库存数
                    stringRedisTemplate.boundValueOps(skuStockKey)
                            .set(sku.getSeckillStock()+"",60*60*4+ ran.nextInt(60*30), TimeUnit.SECONDS);
                    log.info("成功为{}号商品添加库存",sku.getSkuId());
                }
            }
            //  在遍历spu对象的集合中 , 将当前spu包含所有sku保存到redis后,要为spu生成随机码
            //   任何请求都是访问控制器的路径,正常输入

            // // 仍然在遍历所有Spu对象的集合中
            //   将当前Spu包含所有sku保存到Redis之后
            //   我们要为Spu生成随机码
            //   无论任何请求都是访问控制器的路径,秒杀购买商品也是
            //   正常情况下我们输入的路径中,包含要购买商品的id即可
            //   例如 /seckill/spu/,如果这个商品的id已经暴露,
            //   那么就可能有人在秒杀未开始前就访问这个路径
            //   如果不断访问,数据库就需要反复查询这个商品是否在秒杀时间段内,反复查询数据库影响性能
            //   我们要防止这个事情,就要做到秒杀购买商品的路径,平时就不存在
            //   而在秒杀开始5分钟前,生成随机码,有了随机码,购买秒杀商品的路径才出现
            //   我们的随机码生成后也是保存在Redis中
            //   获得随机码key
            String randomCodeKey = SeckillCacheUtils.getRandCodeKey(spu.getSpuId());
            //  判断随机码是否生成过,没有生成在生成
            if (!redisTemplate.hasKey(randomCodeKey)){
                //   生成随机数,随机数越大越难猜
                int randCode = ran.nextInt(900000)+10000;
                redisTemplate.boundValueOps(randomCodeKey).set(randCode,1,TimeUnit.DAYS);
                log.info("spuId为{}的商品随机码为{}",spu.getSpuId(),randCode);
            }else {
                // 输出spuId对应的随机码用于测试
                String code = redisTemplate.boundValueOps(randomCodeKey).get()+"";
                log.info("spuId为{}的商品随机码为{}",spu.getSpuId(),code);
            }

        }
    }
}
